package com.gpc.hexapods.gl;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.PointF;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

import com.gpc.hexapods.data.Cons;
import com.gpc.hexapods.layer.ControlLayout;
import com.gpc.hexapods.utils.MathUtil;

import java.util.Arrays;


public class GLView extends GLSurfaceView implements SurfaceHolder.Callback, ControlLayout.OnCtrlListener {

    private final static String TAG = "HexGLView";

    private static final int DURATION = 80;
    private static final float FORWARD = 0.25f;
    private static final float ROTATE = 30;
    private static final float UP = 0.18f;
    private static final boolean LEFT = true;
    private static final boolean RIGHT = false;
    private static final float A1 = 120;
    private static final float A2 = 180;
    private static final float A3 = 240;
    private static final float A4 = 60;
    private static final float A5 = 0;
    private static final float A6 = -60;

    private float[] up1 = new float[]{A1, Cons.B, Cons.C, -Cons.X1, Cons.Y1};
    private float[] up2 = new float[]{A2, Cons.B, Cons.C, -Cons.X2, 0};
    private float[] up3 = new float[]{A3, Cons.B, Cons.C, -Cons.X1, -Cons.Y1};
    private float[] up4 = new float[]{A4, Cons.B, Cons.C, Cons.X1, Cons.Y1};
    private float[] up5 = new float[]{A5, Cons.B, Cons.C, Cons.X2, 0};
    private float[] up6 = new float[]{A6, Cons.B, Cons.C, Cons.X1, -Cons.Y1};
    private float[] m1 = up1;
    private float[] m2 = up2;
    private float[] m3 = up3;
    private float[] m4 = up4;
    private float[] m5 = up5;
    private float[] m6 = up6;

    private GLRender mRender;
    private PointF mOnePointF;
    private PointF mTwoPointF;
    private PointF mMovePointF;

    private boolean isRunning;
    private short[] mValue = new short[20];
    private GlListener mGlListener;

    public GLView(Context context) {
        super(context);
        init(context);
    }

    public GLView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public void init(Context context) {
        mOnePointF = new PointF();
        mTwoPointF = new PointF();
        mMovePointF = new PointF();

        setEGLContextClientVersion(2);
        setZOrderOnTop(true);
        setZOrderMediaOverlay(true);

        mRender = new GLRender(context);
        setRenderer(mRender);
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        //setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    public void setListener(GlListener glListener) {
        mGlListener = glListener;
    }

    private float[] getFrontAngle(float forward, float[] angle, boolean left) {
        return getFrontAngle(forward, angle[0], angle[1], angle[2], angle[3], angle[4], left);
    }

    private float[] getFrontAngle(float forward, float aAngle, float bAngle, float cAngle, float x0, float y0, boolean left) {
        float l1 = Cons.L2 * MathUtil.cos(bAngle) + Cons.L3 * MathUtil.cos(180 - bAngle - cAngle);
        float x = l1 * MathUtil.cos(aAngle);
        float y = l1 * MathUtil.sin(aAngle) - forward;
        float l2 = (float) Math.sqrt(x * x + y * y);
        float aAng = (float) MathUtil.asin(y / l2);
        if (left) {
            aAng = 180 - aAng;
        }
        float h = Cons.L3 * MathUtil.sin(180 - bAngle - cAngle) - Cons.L2 * MathUtil.sin(bAngle);
        float d = (float) Math.sqrt(h * h + l2 * l2);
        float a = (float) (Cons.L2 * Cons.L2 - Cons.L3 * Cons.L3 + d * d) / (2 * d);
        float c = (float) Math.sqrt(Cons.L2 * Cons.L2 - a * a);
        float H = c * l2 / d - a * h / d;
        float bAng = MathUtil.asin(H / Cons.L2);
        float cAng = 180 - bAng - MathUtil.asin((H + h) / Cons.L3);
        float[] value = new float[]{aAng, bAng, cAng, x0, y0};
        //Log.i(TAG, "getValue -> value : " + Arrays.toString(value));
        return value;
    }

    private float[] getUpAngle(float up, float[] angle) {
        return getUpAngle(up, angle[0], angle[1], angle[2], angle[3], angle[4]);
    }

    private float[] getUpAngle(float up, float aAngle, float bAngle, float cAngle, float x0, float y0) {
        float l = Cons.L2 * MathUtil.cos(bAngle) + Cons.L3 * MathUtil.cos(180 - bAngle - cAngle);
        float h = Cons.L3 * MathUtil.sin(180 - bAngle - cAngle) - Cons.L2 * MathUtil.sin(bAngle);
        float H = h - up;
        float d = (float) Math.sqrt(H * H + l * l);
        float a = (float) (Cons.L2 * Cons.L2 - Cons.L3 * Cons.L3 + d * d) / (2 * d);
        float c = (float) Math.sqrt(Cons.L2 * Cons.L2 - a * a);
        float H1 = c * l / d - a * H / d;
        float bAng = MathUtil.asin(H1 / Cons.L2);
        float cAng = 180 - bAng - MathUtil.asin((H + H1) / Cons.L3);
        float[] value = new float[]{aAngle, bAng, cAng, x0, y0};
        //Log.i(TAG, "getValue -> value : " + Arrays.toString(value));
        return value;
    }

    private float[] getRotateAngle(float rotate, float[] angle, boolean left) {
        return getRotateAngle(rotate, angle[0], angle[1], angle[2], angle[3], angle[4], left);
    }

    private float[] getRotateAngle(float rotate, float aAngle, float bAngle, float cAngle, float x0, float y0, boolean left) {
        float x1 = x0 * MathUtil.cos(rotate) - y0 * MathUtil.sin(rotate);
        float y1 = y0 * MathUtil.cos(rotate) + x0 * MathUtil.sin(rotate);
        float l1 = Cons.L2 * MathUtil.cos(bAngle) + Cons.L3 * MathUtil.cos(180 - bAngle - cAngle);
        float x2 = l1 * MathUtil.cos(aAngle) + x0 - x1;
        float y2 = l1 * MathUtil.sin(aAngle) + y0 - y1;
        float l2 = (float) Math.sqrt(x2 * x2 + y2 * y2);
        float aAng = MathUtil.asin( y2 / l2);
        if (left) {
            aAng = 180 - aAng;
        }
        float h = Cons.L3 * MathUtil.sin(180 - bAngle - cAngle) - Cons.L2 * MathUtil.sin(bAngle);
        float d = (float) Math.sqrt(h * h + l2 * l2);
        float a = (float) (Cons.L2 * Cons.L2 - Cons.L3 * Cons.L3 + d * d) / (2 * d);
        float c = (float) Math.sqrt(Cons.L2 * Cons.L2 - a * a);
        float H = c * l2 / d - a * h / d;
        float bAng = MathUtil.asin(H / Cons.L2);
        float cAng = 180 - bAng - MathUtil.asin((H + h) / Cons.L3);
        float[] value = new float[]{aAng, bAng, cAng, x1, y1};
        //Log.i(TAG, "getValue -> value : " + Arrays.toString(value));
        return value;
    }

    private void saveLastAngle() {
        up1 = m1; up2 = m2; up3 = m3; up4 = m4; up5 = m5; up6 = m6;
        callBack();
    }

    private void callBack() {
        if (mGlListener == null) {
            return;
        }
        mValue[0] = (short) ((Cons.L11 * 10) - getTime(up1[0] - A1));
        mValue[1] = (short) ((Cons.L12 * 10) + getTime(up1[1] - Cons.B));
        mValue[2] = (short) ((Cons.L13 * 10) - getTime(up1[2] - Cons.C));
        mValue[3] = (short) ((Cons.L21 * 10) - getTime(up2[0] - A2));
        mValue[4] = (short) ((Cons.L22 * 10) + getTime(up2[1] - Cons.B));
        mValue[5] = (short) ((Cons.L23 * 10) - getTime(up2[2] - Cons.C));
        mValue[6] = (short) ((Cons.L31 * 10) - getTime(up3[0] - A3));
        mValue[7] = (short) ((Cons.L32 * 10) + getTime(up3[1] - Cons.B));
        mValue[8] = (short) ((Cons.L33 * 10) - getTime(up3[2] - Cons.C));
        mValue[9] = (short) ((Cons.R11 * 10) - getTime(up4[0] - A4));
        mValue[10] = (short) ((Cons.R12 * 10) - getTime(up4[1] - Cons.B));
        mValue[11] = (short) ((Cons.R13 * 10) + getTime(up4[2] - Cons.C));
        mValue[12] = (short) ((Cons.R21 * 10) - getTime(up5[0] - A5));
        mValue[13] = (short) ((Cons.R22 * 10) - getTime(up5[1] - Cons.B));
        mValue[14] = (short) ((Cons.R23 * 10) + getTime(up5[2] - Cons.C));
        mValue[15] = (short) ((Cons.R31 * 10) - getTime(up6[0] - A6));
        mValue[16] = (short) ((Cons.R32 * 10) - getTime(up6[1] - Cons.B));
        mValue[17] = (short) ((Cons.R33 * 10) + getTime(up6[2] - Cons.C));
        mValue[18] = (short) (Cons.HD1 * 10);
        mValue[19] = (short) (Cons.HD2 * 10);
        mGlListener.sendArray(mValue);
    }

    private int getTime(float angle) {
        return Math.round(angle * 2000 / 180);
    }

    private void setRenderAngle() {
        mRender.getM1LegLayer().setAngle(m1);
        mRender.getM2LegLayer().setAngle(m2);
        mRender.getM3LegLayer().setAngle(m3);
        mRender.getM4LegLayer().setAngle(m4);
        mRender.getM5LegLayer().setAngle(m5);
        mRender.getM6LegLayer().setAngle(m6);
    }

    private void doFrontAnnotator(float forward) {
        if (isRunning) {
            return;
        }
        isRunning = true;
        ValueAnimator animator1 = ValueAnimator.ofFloat(0, 1);
        animator1.setDuration(DURATION);
        animator1.addUpdateListener(animation -> {
            float value = (float) animation.getAnimatedValue() * UP;
            m1 = getUpAngle(value, up1);
            m3 = getUpAngle(value, up3);
            m5 = getUpAngle(value, up5);
            setRenderAngle();
            requestRender();
        });
        animator1.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        ValueAnimator animator2 = ValueAnimator.ofFloat(0, 1);
        animator2.setDuration(DURATION);
        animator2.addUpdateListener(animation -> {
            float value = (float) animation.getAnimatedValue() * forward;
            m2 = getFrontAngle(value, up2, LEFT);
            m4 = getFrontAngle(value, up4, RIGHT);
            m6 = getFrontAngle(value, up6, RIGHT);

            m1 = getFrontAngle(-value, up1, LEFT);
            m3 = getFrontAngle(-value, up3, LEFT);
            m5 = getFrontAngle(-value, up5, RIGHT);

            setRenderAngle();
            requestRender();
        });
        animator2.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        ValueAnimator animator3 = ValueAnimator.ofFloat(0, 1);
        animator3.setDuration(DURATION);
        animator3.addUpdateListener(animation -> {
            float value = - (float) animation.getAnimatedValue() * UP;
            m1 = getUpAngle(value, up1);
            m3 = getUpAngle(value, up3);
            m5 = getUpAngle(value, up5);
            setRenderAngle();
            requestRender();
        });
        animator3.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        ValueAnimator animator4 = ValueAnimator.ofFloat(0, 1);
        animator4.setDuration(DURATION);
        animator4.addUpdateListener(animation -> {
            float value = (float) animation.getAnimatedValue() * UP;
            m2 = getUpAngle(value, up2);
            m4 = getUpAngle(value, up4);
            m6 = getUpAngle(value, up6);
            setRenderAngle();
            requestRender();
        });
        animator4.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        ValueAnimator animator5 = ValueAnimator.ofFloat(0, 1);
        animator5.setDuration(DURATION);
        animator5.addUpdateListener(animation -> {
            float value = (float) animation.getAnimatedValue() * forward;
            m2 = getFrontAngle(-value, up2, LEFT);
            m4 = getFrontAngle(-value, up4, RIGHT);
            m6 = getFrontAngle(-value, up6, RIGHT);

            m1 = getFrontAngle(value, up1, LEFT);
            m3 = getFrontAngle(value, up3, LEFT);
            m5 = getFrontAngle(value, up5, RIGHT);
            setRenderAngle();
            requestRender();
        });
        animator5.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        ValueAnimator animator6 = ValueAnimator.ofFloat(0, 1);
        animator6.setDuration(DURATION);
        animator6.addUpdateListener(animation -> {
            float value = - (float) animation.getAnimatedValue() * UP;
            m2 = getUpAngle(value, up2);
            m4 = getUpAngle(value, up4);
            m6 = getUpAngle(value, up6);
            setRenderAngle();
            requestRender();
        });
        animator6.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playSequentially(animator1, animator2, animator3, animator4, animator5, animator6);
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                Log.i(TAG, "onAnimationEnd -> up1 : " + Arrays.toString(up1));
                Log.i(TAG, "onAnimationEnd -> up2 : " + Arrays.toString(up2));
                Log.i(TAG, "onAnimationEnd -> up3 : " + Arrays.toString(up3));
                Log.i(TAG, "onAnimationEnd -> up4 : " + Arrays.toString(up4));
                Log.i(TAG, "onAnimationEnd -> up5 : " + Arrays.toString(up5));
                Log.i(TAG, "onAnimationEnd -> up6 : " + Arrays.toString(up6));
                isRunning = false;
                //animatorSet.start();
            }
        });
        animatorSet.start();
    }

    private void doRotateAnnotator(float rotate) {
        if (isRunning) {
            return;
        }
        isRunning = true;
        ValueAnimator animator1 = ValueAnimator.ofFloat(0, 1);
        animator1.setDuration(DURATION);
        animator1.addUpdateListener(animation -> {
            float value = (float) animation.getAnimatedValue() * UP;
            m1 = getUpAngle(value, up1);
            m3 = getUpAngle(value, up3);
            m5 = getUpAngle(value, up5);
            setRenderAngle();
            requestRender();
        });
        animator1.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        ValueAnimator animator2 = ValueAnimator.ofFloat(0, 1);
        animator2.setDuration(DURATION);
        animator2.addUpdateListener(animation -> {
            float value = (float) animation.getAnimatedValue() * rotate;
            m2 = getRotateAngle(value, up2, LEFT);
            m4 = getRotateAngle(value, up4, RIGHT);
            m6 = getRotateAngle(value, up6, RIGHT);

            m1 = getRotateAngle(-value, up1, LEFT);
            m3 = getRotateAngle(-value, up3, LEFT);
            m5 = getRotateAngle(-value, up5, RIGHT);
            setRenderAngle();
            requestRender();
        });
        animator2.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        ValueAnimator animator3 = ValueAnimator.ofFloat(0, 1);
        animator3.setDuration(DURATION);
        animator3.addUpdateListener(animation -> {
            float value = - (float) animation.getAnimatedValue() * UP;
            m1 = getUpAngle(value, up1);
            m3 = getUpAngle(value, up3);
            m5 = getUpAngle(value, up5);
            setRenderAngle();
            requestRender();
        });
        animator3.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        ValueAnimator animator4 = ValueAnimator.ofFloat(0, 1);
        animator4.setDuration(DURATION);
        animator4.addUpdateListener(animation -> {
            float value = (float) animation.getAnimatedValue() * UP;
            m2 = getUpAngle(value, up2);
            m4 = getUpAngle(value, up4);
            m6 = getUpAngle(value, up6);
            setRenderAngle();
            requestRender();
        });
        animator4.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        ValueAnimator animator5 = ValueAnimator.ofFloat(0, 1);
        animator5.setDuration(DURATION);
        animator5.addUpdateListener(animation -> {
            float value = (float) animation.getAnimatedValue() * rotate;
            m2 = getRotateAngle(-value, up2, LEFT);
            m4 = getRotateAngle(-value, up4, RIGHT);
            m6 = getRotateAngle(-value, up6, RIGHT);

            m1 = getRotateAngle(value, up1, LEFT);
            m3 = getRotateAngle(value, up3, LEFT);
            m5 = getRotateAngle(value, up5, RIGHT);
            setRenderAngle();
            requestRender();
        });
        animator5.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });

        ValueAnimator animator6 = ValueAnimator.ofFloat(0, 1);
        animator6.setDuration(DURATION);
        animator6.addUpdateListener(animation -> {
            float value = - (float) animation.getAnimatedValue() * UP;
            m2 = getUpAngle(value, up2);
            m4 = getUpAngle(value, up4);
            m6 = getUpAngle(value, up6);
            setRenderAngle();
            requestRender();
        });
        animator6.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                saveLastAngle();
            }
        });


        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playSequentially(animator1, animator2, animator3, animator4, animator5, animator6);
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                Log.i(TAG, "onAnimationEnd -> up1 : " + Arrays.toString(up1));
                Log.i(TAG, "onAnimationEnd -> up2 : " + Arrays.toString(up2));
                Log.i(TAG, "onAnimationEnd -> up3 : " + Arrays.toString(up3));
                Log.i(TAG, "onAnimationEnd -> up4 : " + Arrays.toString(up4));
                Log.i(TAG, "onAnimationEnd -> up5 : " + Arrays.toString(up5));
                Log.i(TAG, "onAnimationEnd -> up6 : " + Arrays.toString(up6));
                isRunning = false;
                //animatorSet.start();
            }
        });
        animatorSet.start();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int eventAction = event.getAction();
        Log.i(TAG, "onTouchEvent -> eventAction : " + eventAction);
        switch (eventAction) {
            case MotionEvent.ACTION_DOWN:
                mOnePointF.set(event.getX(), event.getY());
                Log.i(TAG, "onTouchEvent -> mOnePointF : " + mOnePointF);
                break;

            case MotionEvent.ACTION_MOVE:
                mMovePointF.set(event.getX(), event.getY());
                Log.i(TAG, "onTouchEvent -> mMovePointF : " + mMovePointF);
                float diffX = mMovePointF.x - mOnePointF.x;
                float diffY = mMovePointF.y - mOnePointF.y;
                float scale = 0.01f;
                if (Math.abs(diffX) > Math.abs(diffY)) {
                    mRender.rotate(GLRender.Z, -diffX * scale);
                } else {
                    mRender.rotate(GLRender.X, -diffY * scale);
                }
                break;

            case MotionEvent.ACTION_UP:
                break;

            case MotionEvent.ACTION_POINTER_DOWN | 0x0100:
                //mRender.setData();
                doFrontAnnotator(FORWARD);
//                doRotateAnnotator(ROTATE);
                break;

            default:
                break;
        }

        requestRender();
        return true;
    }

    @Override
    public void onCtrlListener(float linearSpeed, float angularSpeed) {
        Log.i(TAG, "onCtrlListener -> linearSpeed : " + linearSpeed + ", angularSpeed : " + angularSpeed);
        if (linearSpeed != 0) {
            doFrontAnnotator(FORWARD * linearSpeed);
        } else if (angularSpeed != 0) {
            doRotateAnnotator(ROTATE * angularSpeed);
        }
    }
}
